﻿using System;
using System.IO;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {

        #region 填充wav文件头

        /// <summary>
        /// 填充wav头(前提是wav数据流的起始位置头大小的空余空间)
        /// </summary>
        /// <param name="wavFilePath">wav文件路径</param>
        /// <param name="encoding">wav数据编码</param>
        /// <param name="sampleBits">采样位数(仅Pcm编码有效,其它编码时填任意值)</param>
        /// <param name="sampleRate">采样率</param>
        /// <param name="chs">声道数</param>
        /// <param name="waveDataLength">wave数据长度(数据流总长度-头大小),等于0表示所有数据</param>
        /// <param name="writeFact">仅对IeeeFloat编码有效,是否写入fact数据块[true:写入;false:不写入]</param>
        public static void FillWavHead(string wavFilePath, WaveEncoding encoding, int sampleBits, int sampleRate, short chs, uint waveDataLength = 0, bool writeFact = false)
        {
            using (var fileStream = new FileStream(wavFilePath, FileMode.Open, FileAccess.Write))
            {
                PrimitiveFillWavHead(fileStream, encoding, sampleBits, sampleRate, chs, waveDataLength, writeFact);
            }
        }

        /// <summary>
        /// 填充wav头(前提是wav数据流的起始位置头大小的空余空间)
        /// </summary>
        /// <param name="wavStream">wav数据流</param>
        /// <param name="encoding">wav数据编码</param>
        /// <param name="sampleBits">采样位数(仅Pcm编码有效,其它编码时填任意值)</param>
        /// <param name="sampleRate">采样率</param>
        /// <param name="chs">声道数</param>
        /// <param name="waveDataLength">wave数据长度(数据流总长度-头大小),等于0表示所有数据</param>
        /// <param name="writeFact">仅对IeeeFloat编码有效,是否写入fact数据块[true:写入;false:不写入]</param>
        public static void FillWavHead(Stream wavStream, WaveEncoding encoding, int sampleBits, int sampleRate, short chs, uint waveDataLength = 0, bool writeFact = false)
        {
            if (wavStream == null)
            {
                throw new ArgumentNullException(nameof(wavStream));
            }

            if (!wavStream.CanSeek && wavStream.Length != 0)
            {
                throw new ArgumentException("流不支持调整写入位置.", nameof(wavStream));
            }

            if (!wavStream.CanWrite)
            {
                throw new ArgumentException("流不可写入.", nameof(wavStream));
            }

            PrimitiveFillWavHead(wavStream, encoding, sampleBits, sampleRate, chs, waveDataLength, writeFact);
        }

        private static void PrimitiveFillWavHead(Stream wavStream, WaveEncoding encoding, int sampleBits, int sampleRate, short chs, uint waveDataLength, bool writeFact)
        {
            switch (encoding)
            {
                case WaveEncoding.Pcm:
                    if (waveDataLength == 0)
                    {
                        waveDataLength = (uint)(wavStream.Length - _WAVE_HEAD_SIZE);
                    }

                    FillPcmWavHead(wavStream, encoding, sampleBits, sampleRate, chs, waveDataLength);
                    break;
                case WaveEncoding.IeeeFloat:
                    if (waveDataLength == 0)
                    {
                        waveDataLength = (uint)(wavStream.Length - _WAVE_HEAD_SIZE58);
                    }

                    if (writeFact)
                    {
                        FillPcmWavIeeeFloatHead(wavStream, sampleRate, chs, waveDataLength);
                    }
                    else
                    {
                        FillPcmWavHead(wavStream, encoding, sampleBits, sampleRate, chs, waveDataLength);
                    }
                    break;
                case WaveEncoding.ALaw:
                case WaveEncoding.MuLaw:
                    if (waveDataLength == 0)
                    {
                        waveDataLength = (uint)(wavStream.Length - _WAVE_HEAD_SIZE58);
                    }

                    FillALawAndMuLawHead(wavStream, encoding, sampleRate, chs, waveDataLength);
                    break;
                case WaveEncoding.Adpcm:
                    if (waveDataLength == 0)
                    {
                        waveDataLength = (uint)(wavStream.Length - _WAVE_HEAD_SIZE90);
                    }

                    FillAdpcmWavHead(wavStream, sampleRate, chs, waveDataLength);
                    break;
                case WaveEncoding.IbmCvsd:
                    if (waveDataLength == 0)
                    {
                        waveDataLength = (uint)(wavStream.Length - _WAVE_HEAD_SIZE0);
                    }

                    FillCSVDWavHead(wavStream, sampleRate, chs, waveDataLength);
                    break;
                default:
                    throw new NotImplementedException($"未实现的wave数据编码类型\"{encoding.ToString()}\"头填充.");
            }
        }

        private static void FillCSVDWavHead(Stream wavStream, int sampleRate, short chs, uint waveDataLength)
        {
            //const short extraSize = 32;

            if (wavStream.Position != 0L)
            {
                wavStream.Position = 0L;
            }

            var writer = new BinaryWriter(wavStream);

            //RIFF，资源交换文件标志,4byte
            writer.Write('R');
            writer.Write('I');
            writer.Write('F');
            writer.Write('F');

            //从RIFF下一个地址开始到文件尾的总字节数,4byte,文件总大小减去8字节
            uint length = waveDataLength + _WAVE_HEAD_SIZE90 - 8;//44字节头
            writer.Write(length);

            //WAVE，代表wav文件格式,4byte
            writer.Write('W');
            writer.Write('A');
            writer.Write('V');
            writer.Write('E');

            //fmt ，波形格式标志（fmt ）,4byte
            writer.Write('f');
            writer.Write('m');
            writer.Write('t');
            writer.Write(' ');

            throw new NotImplementedException($"{WaveEncoding.IbmCvsd}未实现的头填充.");
        }

        private static void FillAdpcmWavHead(Stream wavStream, int sampleRate, short chs, uint waveDataLength)
        {
            const short extraSize = 32;

            if (wavStream.Position != 0L)
            {
                wavStream.Position = 0L;
            }

            var writer = new BinaryWriter(wavStream);

            //RIFF，资源交换文件标志,4byte
            writer.Write('R');
            writer.Write('I');
            writer.Write('F');
            writer.Write('F');

            //从RIFF下一个地址开始到文件尾的总字节数,4byte,文件总大小减去8字节
            uint length = waveDataLength + _WAVE_HEAD_SIZE90 - 8;//44字节头
            writer.Write(length);

            //WAVE，代表wav文件格式,4byte
            writer.Write('W');
            writer.Write('A');
            writer.Write('V');
            writer.Write('E');

            //fmt ，波形格式标志（fmt ）,4byte
            writer.Write('f');
            writer.Write('m');
            writer.Write('t');
            writer.Write(' ');




            //块对齐
            short sampleBits = 4, samplesPerBlock;
            short blockAlign = CaculateAdpcmBlockAlign(sampleRate);
            int trunkSize = CaculateAdpcmTrunkSize(sampleRate, blockAlign, chs, sampleBits, out samplesPerBlock);

            //格式块的大小
            writer.Write((int)(18 + extraSize));

            //编码格式。这里是0001H,2byte
            writer.Write((Int16)WaveEncoding.Adpcm);

            //[声道数] 1为单声道，2为双声道,2byte
            writer.Write(chs);

            //[采样频率],4byte
            writer.Write(sampleRate);
            writer.Write(trunkSize);
            writer.Write(blockAlign);

            //[采样位数] 样本数据位数，0010H即16，一个量化样本占2byte(8位/16位/32位)
            writer.Write((Int16)sampleBits);
            writer.Write((Int16)extraSize);



            writer.Write(samplesPerBlock);
            short numCoeff = 7;
            writer.Write(numCoeff);

            short[] coefficients = new short[14]
            {
                256,0,512,-256,0,0,192,64,240,0,460,-208,392,-232
            };
            foreach (short coefficient in coefficients)
            {
                writer.Write(coefficient);
            }


            //fact
            writer.Write('f');
            writer.Write('a');
            writer.Write('c');
            writer.Write('t');
            writer.Write((int)4);
            writer.Write((int)((waveDataLength * 8) / sampleBits)); // number of samples

            //data标志,4byte
            writer.Write('d');
            writer.Write('a');
            writer.Write('t');
            writer.Write('a');

            //Wav文件实际音频数据所占的大小，这里是001437C8H即1325000，再加上2CH就正好是1325044，整个文件的大小,4byte               
            writer.Write(waveDataLength);

            wavStream.Flush();
        }

        private static void FillPcmWavHead(Stream wavStream, WaveEncoding encoding, int sampleBits, int sampleRate, short chs, uint waveDataLength)
        {
            if (wavStream.Position != 0L)
            {
                wavStream.Position = 0L;
            }

            var writer = new BinaryWriter(wavStream);

            //RIFF，资源交换文件标志,4byte
            writer.Write('R');
            writer.Write('I');
            writer.Write('F');
            writer.Write('F');

            //从RIFF下一个地址开始到文件尾的总字节数,4byte,文件总大小减去8字节
            uint length = waveDataLength + _WAVE_HEAD_SIZE - 8;//44字节头
            writer.Write(length);

            //WAVE，代表wav文件格式,4byte
            writer.Write('W');
            writer.Write('A');
            writer.Write('V');
            writer.Write('E');

            //fmt ，波形格式标志（fmt ）,4byte
            writer.Write('f');
            writer.Write('m');
            writer.Write('t');
            writer.Write(' ');



            //格式块的大小，对于PCM编码通常为16
            writer.Write((int)16);

            //编码格式。这里是0001H,2byte
            writer.Write((Int16)encoding);

            //[声道数] 1为单声道，2为双声道,2byte
            writer.Write(chs);

            //[采样频率],4byte
            writer.Write(sampleRate);

            //[播放速度(传输速度)] 数据传输率,含义:播波形数据传输速率（每秒平均字节数,每秒所需播放字节数）,Bite率;[数据类型:long] 注: Bite率=采样频率*声道数*采样样本位数/8
            //数据传输速率，计算公式为：采样率×声道数×采样位数/8
            int trunkSize = chs * sampleBits * sampleRate / 8;
            writer.Write(trunkSize);

            //块对齐=声道数*每次采样得到的样本位数/8，0002H，也就是2=1*16/8,2byte
            short blockAlign = (short)(chs * sampleBits / 8);
            writer.Write(blockAlign);

            //[采样位数] 样本数据位数，0010H即16，一个量化样本占2byte(8位/16位/32位)
            writer.Write((Int16)sampleBits);

            //data标志,4byte
            writer.Write('d');
            writer.Write('a');
            writer.Write('t');
            writer.Write('a');

            //Wav文件实际音频数据所占的大小，这里是001437C8H即1325000，再加上2CH就正好是1325044，整个文件的大小,4byte               
            writer.Write(waveDataLength);

            wavStream.Flush();
        }

        private static void FillPcmWavIeeeFloatHead(Stream wavStream, int sampleRate, short chs, uint waveDataLength)
        {
            if (wavStream == null)
            {
                throw new ArgumentNullException(nameof(wavStream));
            }

            const short extraSize = 0;
            const int sampleBits = DataSampleBits.BIT32;

            if (wavStream.Position != 0L)
            {
                wavStream.Position = 0L;
            }

            var writer = new BinaryWriter(wavStream);

            //RIFF，资源交换文件标志,4byte
            writer.Write('R');
            writer.Write('I');
            writer.Write('F');
            writer.Write('F');

            //从RIFF下一个地址开始到文件尾的总字节数,4byte,文件总大小减去8字节
            uint length = waveDataLength + _WAVE_HEAD_SIZE58 - 8;//44字节头
            writer.Write(length);

            //WAVE，代表wav文件格式,4byte
            writer.Write('W');
            writer.Write('A');
            writer.Write('V');
            writer.Write('E');

            //fmt ，波形格式标志（fmt ）,4byte
            writer.Write('f');
            writer.Write('m');
            writer.Write('t');
            writer.Write(' ');



            //格式块的大小，对于PCM编码通常为16
            writer.Write((int)(18 + extraSize));

            //编码格式。这里是0001H,2byte
            writer.Write((Int16)WaveEncoding.IeeeFloat);

            //[声道数] 1为单声道，2为双声道,2byte
            writer.Write(chs);

            //[采样频率],4byte
            writer.Write(sampleRate);

            //[播放速度(传输速度)] 数据传输率,含义:播波形数据传输速率（每秒平均字节数,每秒所需播放字节数）,Bite率;[数据类型:long] 注: Bite率=采样频率*声道数*采样样本位数/8
            //数据传输速率，计算公式为：采样率×声道数×采样位数/8
            int trunkSize = chs * sampleBits * sampleRate / 8;
            writer.Write(trunkSize);

            //块对齐=声道数*每次采样得到的样本位数/8，0002H，也就是2=1*16/8,2byte
            short blockAlign = (short)(chs * sampleBits / 8);
            writer.Write(blockAlign);

            //[采样位数] 样本数据位数，0010H即16，一个量化样本占2byte(8位/16位/32位)
            writer.Write((Int16)sampleBits);

            writer.Write((Int16)extraSize);

            //fact
            writer.Write('f');
            writer.Write('a');
            writer.Write('c');
            writer.Write('t');

            writer.Write((int)4);
            writer.Write((int)((waveDataLength * 8) / sampleBits)); // number of samples

            //data标志,4byte
            writer.Write('d');
            writer.Write('a');
            writer.Write('t');
            writer.Write('a');

            //Wav文件实际音频数据所占的大小，这里是001437C8H即1325000，再加上2CH就正好是1325044，整个文件的大小,4byte               
            writer.Write(waveDataLength);

            wavStream.Flush();
        }

        private static void FillALawAndMuLawHead(Stream wavStream, WaveEncoding encoding, int sampleRate, short chs, uint waveDataLength)
        {
            if (wavStream.Position != 0L)
            {
                wavStream.Position = 0L;
            }

            const short extraSize = 0;
            const int sampleBits = DataSampleBits.BIT8;

            var writer = new BinaryWriter(wavStream);

            //RIFF，资源交换文件标志,4byte
            writer.Write('R');
            writer.Write('I');
            writer.Write('F');
            writer.Write('F');

            //从RIFF下一个地址开始到文件尾的总字节数,4byte,文件总大小减去8字节
            uint length = waveDataLength + _WAVE_HEAD_SIZE58 - 8;//44字节头
            writer.Write(length);

            //WAVE，代表wav文件格式,4byte
            writer.Write('W');
            writer.Write('A');
            writer.Write('V');
            writer.Write('E');

            //fmt ，波形格式标志（fmt ）,4byte
            writer.Write('f');
            writer.Write('m');
            writer.Write('t');
            writer.Write(' ');




            //格式块的大小，wave format length
            writer.Write((int)(18 + extraSize));

            //编码格式。这里是0001H,2byte
            writer.Write((short)encoding);

            //[声道数] 1为单声道，2为双声道,2byte
            writer.Write(chs);

            //[采样频率],4byte
            writer.Write(sampleRate);

            //[播放速度(传输速度)] 数据传输率,含义:播波形数据传输速率（每秒平均字节数,每秒所需播放字节数）,Bite率;[数据类型:long] 注: Bite率=采样频率*声道数*采样样本位数/8
            //数据传输速率，计算公式为：采样率×声道数×采样位数/8
            int averageBytesPerSecond = chs * sampleRate;
            writer.Write(averageBytesPerSecond);

            //对齐块,2byte,用声道数填充
            writer.Write(chs);

            //[采样位数] 样本数据位数，0010H即16，一个量化样本占2byte(8位/16位/32位)
            writer.Write((short)sampleBits);
            writer.Write(extraSize);


            //fact
            writer.Write('f');
            writer.Write('a');
            writer.Write('c');
            writer.Write('t');
            writer.Write((int)4);
            writer.Write((int)(waveDataLength * 8 / (sampleBits * chs)));


            //data标志,4byte
            writer.Write('d');
            writer.Write('a');
            writer.Write('t');
            writer.Write('a');
            writer.Write(waveDataLength);

        }

        #endregion

    }
}
